package pinkblob.tools;

import java.util.*;

import java.io.*;

import javax.mail.*;
import javax.mail.internet.*;

import javax.xml.parsers.*;

import org.apache.log4j.Logger;
import org.w3c.dom.*;
import org.xml.sax.InputSource;

import pinkblob.entity.Attachment;
import pinkblob.event.DatabaseFileHandler;
import pinkblob.event.ProjectInformationHandler;
import pinkblob.log.Log;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * static class to handle Mails for Infinity
 * 
 * @version 0.5
 */
public final class Fetchmail {

	private static String MAILSERVER = "pinkblob.kicks-ass.net";
	private static String MAIL_USERNAME = "workflow@pinkblob.kicks-ass.net";
	private static String MAIL_PASSWORD = "PB.123456";

	// private static String MAILSERVER = "localhost";
	// private static String MAIL_USERNAME = "raffel";
	// private static String MAIL_PASSWORD = "123456";

	private static Session session = null;
	private static Store store = null;
	private static Folder folder = null;

	private static Logger log;

	/**
	 * The static block configures the log4j system for this class
	 */
	static {
		log = new Log().getLogger(Fetchmail.class);
	}

	public static void complete() {
	}

	/**
	 * this is an internal class for handling the incoming mails
	 */
	private static class PBMail {
		public PBMail() {
			log.debug("Creating instance of PBMail");
			body = null;
			attachments = new ArrayList<Attachment>();
		}

		String body, subject;
		ArrayList<Attachment> attachments;
	}

	/**
	 * This is the essential method which is called when data shall be retrieved
	 * 
	 * @param subject
	 * @return
	 */
	private static synchronized Map<String, Object> loadProjectData(
			String subject) {
		log
				.info("===================================================================-");
		log.info("Start fetching mail");

		Map<String, Object> mail = new HashMap<String, Object>();
		Message[] messages = null;

		// connect to mailserver and retrieve message
		messages = getMessages();
		// TODO: This should throw an exception imho
		if (messages == null) {
			log.error("Messages is null");
			return null;
		}
		if (messages.length == 0) {
			log.warn("No messages received!");
		} else {
			log.debug("Mails fetched - evaluate Mails");
			// evaluate email on subject
			// the first successful email will be used
			if (folder == null) {
				log.error("FOLDER IS NULL!!!");
			}
			PBMail pbmail = evaluateEmailsBySubject(messages, subject);

			try {
				if (pbmail == null || pbmail.body.equals("")) {
					return new HashMap<String, Object>();
				}
				mail = parse(pbmail.body);

				int projectID = Integer
						.parseInt((String) mail.get("ProjectID"));
				saveAttachments(projectID, pbmail.attachments);

			} catch (Exception e) {
				System.out.println("Error, while parsing mail");
				e.printStackTrace();
				log.error("Error while parsing mail!");
			}
		}
		return mail;
	}

	/**
	 * this method connects to the mail server and retrieves the mails.
	 * 
	 * @param store
	 * @param folder
	 * @return
	 */
	private static synchronized Message[] getMessages() {
		Message[] result = null;
		log.debug("retrieving messages...");
		for (int tries = 1; tries <= 3; tries++) {
			try {
				log.debug("Open Mailserver Connection. Tries: "
						+ String.valueOf(tries));
				if (session == null) {
					session = Session.getDefaultInstance(new Properties());
				}
				store = session.getStore("pop3");
				store.connect(MAILSERVER, MAIL_USERNAME, MAIL_PASSWORD);
				folder = store.getFolder("INBOX");
				folder.open(Folder.READ_WRITE);
				result = folder.getMessages();
				log.debug(folder);

				break;
			} catch (Exception e) {
				log.error("Unable to establish Mailserver connection");
				e.printStackTrace();
				log.error("Wait 1 Second and try again");
				try {
					Thread.sleep(1000l);
				} catch (Exception ex) {
				}
			}
		}
		if (result == null) {
			// TODO: Cancel Workflow??
			log.fatal("ERROR: Mailserver connection could not be established!");
		}
		return result;
	}

	/**
	 * Multipart messages have to be worked seperately as we have to go through
	 * all the parts.
	 * 
	 * @param mail
	 * @param message
	 * @throws IOException
	 * @throws MessagingException
	 */
	private static synchronized void handleMultipart(PBMail mail,
			Message message) throws IOException, MessagingException {

		Multipart mp = (Multipart) message.getContent();

		for (int j = 0; j < mp.getCount(); j++) {
			Part part = mp.getBodyPart(j);
			MimeBodyPart mimePart = (MimeBodyPart) part;

			//if (mimePart.isMimeType("text/plain")) {
			if (mail.body == null && mimePart.isMimeType("text/plain")) {
				// this part is the mailbody
				log.debug("reading mail body:");
				mail.body = mimePart.getContent().toString();
				log.debug(mail.body);
			} else {
				// this part is an attachment
				log.debug("reading attachment:");

				String filename = mimePart.getFileName();
				long filedate = new Date().getTime();
				byte[] file = new byte[mimePart.getSize()];
				mimePart.getInputStream().read(file);
				log.debug("File: " + filename + "; size: " + file.length
						+ " KB; Date: " + new Date(filedate).toString());
				mail.attachments.add(new Attachment(filename, filedate, file));
			}
		}
	}

	/**
	 * This method evaluates all emails on their subject to decide wheter they
	 * are interesting. the first success will be the rsult
	 * 
	 * @param messages
	 * @param subject
	 * @return
	 */
	private static synchronized PBMail evaluateEmailsBySubject(
			Message[] messages, String subject) {
		PBMail result = null;
		for (Message message : messages) {
			try {
				PBMail mail = new PBMail();
				mail.subject = message.getSubject();
				if (!mail.subject.equals(subject)) {
					log.info("Mail not relevant to Subject!");
					continue;
				}

				log.info("relevant mail found");
				if (message.getContentType().contains("multipart/mixed")) {
					// Message is multipart
					log.debug("we got a multipart message");
					handleMultipart(mail, message);
				} else {
					// Message is only text
					log.debug("we got a text only message");
					mail.body = message.getContent().toString();
				}

				log.debug("set deleted flag");
				message.setFlag(Flags.Flag.DELETED, true);

				// close connection
				try {
					if (folder != null) {
						log.debug("Closing folder and store");
						folder.close(true);
						store.close();
					} else {
						log.warn("---> Folder is NULL.");
					}
				} catch (MessagingException e) {
					log.error("Error while closing folder and store.");
					e.printStackTrace();
				} finally {
					folder = null;
					store = null;
				}

				if (session != null) {
					session = null;
					log.info("Session has been closed");
				}

				result = mail;
				break;
			} catch (Exception e) {
				log.error("Error, while processing Mail");
				e.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * This method is to parse the XML within an email body
	 * 
	 * @param mailbody
	 * @return
	 * @throws Exception
	 */
	private static Map<String, Object> parse(String mailbody) throws Exception {
		Map<String, Object> mail = new HashMap<String, Object>();

		// Here: Parsing of the XML-Body!!!! ----------------------
		log.debug("Parse XML body");
		log.debug("Body: "+mailbody);
		Document xmlDoc = DocumentBuilderFactory.newInstance()
				.newDocumentBuilder().parse(
						new InputSource(new StringReader(mailbody.trim())));
		log.debug("XML Document created");

		String processid = xmlDoc.getDocumentElement().getAttribute("ID");
		log.debug("Process ID: " + processid);

		// Load project information from db
		ProjectInformationHandler pih = new ProjectInformationHandler();
		mail = pih.getProjectInformationMap(Integer.parseInt(processid));

		// Overwrite db information with mail information
		NodeList nodelist = xmlDoc.getDocumentElement().getChildNodes();
		log.debug(String.valueOf(nodelist.getLength()) + " Child Nodes");
		boolean hasUrgentTag = false;
		Object DescriptionOrRequirements = null;
		for (int i = 0; i < nodelist.getLength(); i++) {
			Node node = nodelist.item(i);
			if (node.getChildNodes().getLength() > 0) {
				String nodeName = node.getNodeName();
				Object nodeValue = node.getChildNodes().item(0).getNodeValue();
				// if (nodeName != null && nodeValue != null) {
				// log.info(nodeName+": "+nodeValue.toString());
				// } else if (nodeName != null){
				// log.info(nodeName);
				// } else {
				// log.warn("No node Name or Value");
				// }
				if (nodeName.equals("UrgentRequest")) {
					hasUrgentTag = true;
					if (nodeValue instanceof String) {
						mail.put("HighPriority", Boolean.valueOf((String)nodeValue));
					} else
						mail.put("HighPriority", (Boolean)nodeValue);
				} else if (nodeName.equals("Description")) {
					DescriptionOrRequirements = nodeValue;
				} else if (nodeName.equals("IsText")) {
					if (nodeValue instanceof String) {
						mail.put("TextDesign", Boolean
								.valueOf((String) nodeValue));
					} else
						mail.put("TextDesign", (Boolean) nodeValue);
				} else if (nodeName.equals("IsGraphic")) {
					if (nodeValue instanceof String) {
						mail.put("GraphicDesign", Boolean
								.valueOf((String) nodeValue));
					} else
						mail.put("GraphicDesign", (Boolean) nodeValue);
				} else if (nodeName.equals("AgreementReply")) {
					NodeList innerNodeList = node.getChildNodes();
					for (int j = 0; j < innerNodeList.getLength(); j++) {
						Node innerNode = innerNodeList.item(j);
						System.out.println(String.valueOf(innerNode));
						if (innerNode.getChildNodes().getLength() > 0) {
							String innerNodeName = innerNode.getNodeName();
							Object innerNodeValue = innerNode.getChildNodes()
									.item(0).getNodeValue();
							if (innerNodeName.equals("State")) {
								mail.put("Agreement_State", innerNodeValue);
							} else if (innerNodeName.equals("Description")) {
								mail.put("Agreement_State_Description",
										innerNodeValue);
							}
						}
					}

				} else if (nodeName.equals("DraftEvaluation")) {
					NodeList innerNodeList = node.getChildNodes();
					for (int j = 0; j < innerNodeList.getLength(); j++) {
						Node innerNode = innerNodeList.item(j);
						System.out.println(String.valueOf(innerNode));
						if (innerNode.getChildNodes().getLength() > 0) {
							String innerNodeName = innerNode.getNodeName();
							Object innerNodeValue = innerNode.getChildNodes()
									.item(0).getNodeValue();
							if (innerNodeName.equals("Accepted")) {
								if (innerNodeValue instanceof String) {
									mail.put("D_DraftAccepted", Boolean.valueOf((String)innerNodeValue));
								} else {
									mail.put("D_DraftAccepted", (Boolean) innerNodeValue);
								}
							} else if (innerNodeName.equals("Comment")) {
								mail.put("D_DraftComment", innerNodeValue);
							}
						}
					}
				}
			}
		}
		// Parsing finished! -----------------------------

		// check RequirementsOrDescription
		// the field AdditionalMailInformation isn't used at the moment
		if (DescriptionOrRequirements != null) {
			if (hasUrgentTag)
				mail.put("DesignRequirements", DescriptionOrRequirements);
			// TODO: mail.put("AdditionalMailInformation",
			// DescriptionOrRequirements);
			else
				log
						.warn("here we throw away the additional mail information! This should be fixed sometime!");
		}

		return mail;
	}

	/**
	 * Saves attachments to a database. This should be moved to the database
	 * package in future.
	 * 
	 * @param projectID
	 * @param attachments
	 */
	private static void saveAttachments(int projectID,
			List<Attachment> attachments) {
		for (Attachment attachment : attachments) {
			try {
				log.info("Save file to Database");
				DatabaseFileHandler db = new DatabaseFileHandler();
				db.saveFile(projectID, attachment.getFilename(), attachment
						.getFiledate(), attachment.getFile());
				log.info("File saved");
			} catch (Exception e) {
				log.error("Attachment could not be saved");
				e.printStackTrace();
			}
		}
	}

	/**
	 * This method manages the Iteration. Iterations are increased, if we
	 * receive a Mail from ISS without the information needed to trigger the
	 * next process.
	 * 
	 * The only exception is, when we request additional information. Then we
	 * have to increase the iterations ourselves, as the trigger to start the
	 * next process is handled by ourselves. Therefore this is done in the
	 * SendMail Class. [tra] 19.1.10
	 * 
	 * @param data
	 */
	@SuppressWarnings("unchecked")
	private static void manageIterations(Map data) {
		ProjectInformationHandler pih = new ProjectInformationHandler();
		Integer iterations = (Integer) data.get("Iterations");
		int status = ((Integer) data.get("Status")).intValue();
		log.info("Iterations Manager - Iterations: " + iterations
				+ "   Status: " + status);
		if (status == 1) {
			// waiting for additional information
			if (((Boolean) data.get("HighPriority")).equals(true)) {
				// this is high priority
				log.debug("High Priority Order. Will reset iterations!");
				iterations = pih.getIterationsBase();
			} else {
				log
						.debug("Got request. Iterations should have been increased,"
								+ " when sending the request for additional requirements!");
			}
		} else if (status == 3) {
			// waiting for the agreement
			if (((String) data.get("Agreement_State")).equals("negotiation")) {
				log.debug("We are still negotiating. Increasing iterations!");
				iterations++;
			} else {
				log
						.debug("Negotiations are completed. Either cancellation or acceptance. Resetting iterations!");
				iterations = pih.getIterationsBase();
			}
		} else if (status == 5) {
			// waiting for draft evaluation
			if (((Boolean) data.get("D_DraftAccepted")).equals(false)) {
				log.debug("Draft was not accepted. Increasing iterations!");
			} else {
				log.debug("Draft has been accepted! Resetting iterations!");
				iterations = pih.getIterationsBase();
			}
		}
		data.put("Iterations", iterations);
		pih.setProjectInformation(data);
	}

	/**
	 * To fetch mail for other subprocesses:
	 * 
	 * 1) copy this method -> change subject in loadProjectData 2) put in
	 * specific stuff like increasing number of iterations 3) add necessary
	 * fields in parse-method
	 * 
	 * @return
	 */
	public static Map<String, Object> getRequirementsMail() {
		log.info("getRequirementsMail() has been called...");
		Map<String, Object> mail = loadProjectData("Requirements");
		if (mail == null)
			return null;

		manageIterations(mail);
		return mail;
	}

	public static Map<String, Object> getAgreementMail() {
		log.info("getAgreementMail() has been called...");
		Map<String, Object> mail = loadProjectData("Agreement Reply");
		if (mail == null)
			return null;
		manageIterations(mail);
		return mail;
	}

	public static Map<String, Object> getEvaluationMail() {
		log.info("getEvaluationMail() has been called...");
		Map<String, Object> mail = loadProjectData("Draft Evaluation");
		if (mail == null)
			return null;
		manageIterations(mail);
		return mail;
	}
	
	public static Map<String, Object> getDeadlineReminderMail() {
		log.info("getDeadlineReminderMail() has been called...");
		Map<String, Object> mail = loadProjectData("Deadline Reminder");
		if (mail == null)
			return null;		
		return mail;
	}

}
